/**
 * MD4.java   2007-2-12
 *
 */
package com._21cn.framework.utils.crypt;

import java.io.UnsupportedEncodingException;
import java.util.Arrays;

/**
 * MD4算法实现
 */
public class MD4 {

    private static int a;
    private static int b;
    private static int c;
    private static int d;
    private static int[] x = new int[16];

    private static int f(int x, int y, int z) {
        return (x & y) | ((~x) & z);
    }

    private static int g(int x, int y, int z) {
        return (x & y) | (x & z) | (y & z);
    }

    private static int h(int x, int y, int z) {
        return x ^ y ^ z;
    }

    private static int lshift(int x, int s) {
        if (s == 0) {
            return x;
        }
        return x << s | x >> 32 - s & 0x7FFFFFFF >> 31 - s;
    }

    private static int round1(int a, int b, int c, int d, int k, int s) {
        return (lshift(a + f(b, c, d) + x[k], s));
    }

    private static int round2(int a, int b, int c, int d, int k, int s) {
        return (lshift(a + g(b, c, d) + x[k] + 0x5A827999, s));
    }

    private static int round3(int a, int b, int c, int d, int k, int s) {
        return (lshift(a + h(b, c, d) + x[k] + 0x6ED9EBA1, s));
    }


    /* this applies md4 to 64 byte chunks */
    private static void mdfour64(int[] m) {
        int j;
        int AA;
        int BB;
        int CC;
        int DD;

        for (j = 0; j < 16; j++)
            x[j] = m[j];

        AA = a;
        BB = b;
        CC = c;
        DD = d;

        a = round1(a, b, c, d, 0, 3);
        d = round1(d, a, b, c, 1, 7);
        c = round1(c, d, a, b, 2, 11);
        b = round1(b, c, d, a, 3, 19);
        a = round1(a, b, c, d, 4, 3);
        d = round1(d, a, b, c, 5, 7);
        c = round1(c, d, a, b, 6, 11);
        b = round1(b, c, d, a, 7, 19);
        a = round1(a, b, c, d, 8, 3);
        d = round1(d, a, b, c, 9, 7);
        c = round1(c, d, a, b, 10, 11);
        b = round1(b, c, d, a, 11, 19);
        a = round1(a, b, c, d, 12, 3);
        d = round1(d, a, b, c, 13, 7);
        c = round1(c, d, a, b, 14, 11);
        b = round1(b, c, d, a, 15, 19);

        a = round2(a, b, c, d, 0, 3);
        d = round2(d, a, b, c, 4, 5);
        c = round2(c, d, a, b, 8, 9);
        b = round2(b, c, d, a, 12, 13);
        a = round2(a, b, c, d, 1, 3);
        d = round2(d, a, b, c, 5, 5);
        c = round2(c, d, a, b, 9, 9);
        b = round2(b, c, d, a, 13, 13);
        a = round2(a, b, c, d, 2, 3);
        d = round2(d, a, b, c, 6, 5);
        c = round2(c, d, a, b, 10, 9);
        b = round2(b, c, d, a, 14, 13);
        a = round2(a, b, c, d, 3, 3);
        d = round2(d, a, b, c, 7, 5);
        c = round2(c, d, a, b, 11, 9);
        b = round2(b, c, d, a, 15, 13);

        a = round3(a, b, c, d, 0, 3);
        d = round3(d, a, b, c, 8, 9);
        c = round3(c, d, a, b, 4, 11);
        b = round3(b, c, d, a, 12, 15);
        a = round3(a, b, c, d, 2, 3);
        d = round3(d, a, b, c, 10, 9);
        c = round3(c, d, a, b, 6, 11);
        b = round3(b, c, d, a, 14, 15);
        a = round3(a, b, c, d, 1, 3);
        d = round3(d, a, b, c, 9, 9);
        c = round3(c, d, a, b, 5, 11);
        b = round3(b, c, d, a, 13, 15);
        a = round3(a, b, c, d, 3, 3);
        d = round3(d, a, b, c, 11, 9);
        c = round3(c, d, a, b, 7, 11);
        b = round3(b, c, d, a, 15, 15);

        a += AA;
        b += BB;
        c += CC;
        d += DD;

        a &= 0xFFFFFFFF;
        b &= 0xFFFFFFFF;
        c &= 0xFFFFFFFF;
        d &= 0xFFFFFFFF;
    }

    private static void copy64(int[] m, byte[] in, int offset) {
        int i;

        for (i = 0; i < 16; i++)
            m[i] = ((in[offset + i * 4 + 3] << 24) & 0xFF000000)
                    | ((in[offset + i * 4 + 2] << 16) & 0xFF0000)
                    | ((in[offset + i * 4 + 1] << 8) & 0xFF00)
                    | (((int) in[offset + i * 4 + 0]) & 0xFF);
    }

    private static void copy64(int[] m, byte[] in) {
        copy64(m, in, 0);
    }


    private static void copy4(byte[] out, int offset, int x) {
        out[offset] = (byte) (x & 0xFF);
        out[1 + offset] = (byte) ((x >> 8) & 0xFF);
        out[2 + offset] = (byte) ((x >> 16) & 0xFF);
        out[3 + offset] = (byte) ((x >> 24) & 0xFF);
    }

    /**
     * 获取字符串的md4的消息摘要
     *
     * @param in 字符串的字节数组
     * @return 16位的字节数组
     */
    public static byte[] digest(byte[] in) {
        byte[] out = new byte[16];
        byte[] buf = new byte[128];
        int n = in.length;
        int[] m = new int[16];
        int b = n * 8;
        int i;
        int offset;

        a = 0x67452301;
        MD4.b = 0xefcdab89;
        c = 0x98badcfe;
        d = 0x10325476;

        offset = 0;
        while (n > 64) {
            copy64(m, in, offset);
            mdfour64(m);
            offset += 64;
            n -= 64;
        }

        for (i = 0; i < 128; i++) {
            buf[i] = (i + offset < in.length) ? in[offset + i] : 0;
        }
        buf[n] = (byte) 0x80;

        if (n <= 55) {
            copy4(buf, 56, b);
            copy64(m, buf);
            mdfour64(m);
        } else {
            copy4(buf, 120, b);
            copy64(m, buf);
            mdfour64(m);
            copy64(m, buf, 64);
            mdfour64(m);
        }

        for (i = 0; i < 128; i++) {
            buf[i] = 0;
        }
        copy64(m, buf);

        copy4(out, 0, a);
        copy4(out, 4, MD4.b);
        copy4(out, 8, c);
        copy4(out, 12, d);

        a = MD4.b = c = d = 0;
        return out;
    }

    public static void main(String[] args) throws UnsupportedEncodingException {
        System.out.println(Arrays.toString(MD4.digest("TST".getBytes("UTF-8"))));
    }

}
